package org.gruposp2p.openid.util;

import javax.servlet.ServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.openid4java.message.AuthRequest;
import org.openid4java.message.AuthSuccess;
import org.openid4java.message.Message;
import org.openid4java.message.MessageExtension;
import org.openid4java.message.Parameter;
import org.openid4java.message.ParameterList;
import org.openid4java.message.ax.AxMessage;
import org.openid4java.message.ax.FetchRequest;
import org.openid4java.message.ax.FetchResponse;
import org.openid4java.message.sreg.SRegMessage;
import org.openid4java.message.sreg.SRegRequest;
import org.openid4java.message.sreg.SRegResponse;
import org.openid4java.server.InMemoryServerAssociationStore;
import org.openid4java.server.ServerManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.codehaus.groovy.grails.commons.ConfigurationHolder;
import org.codehaus.groovy.runtime.GStringImpl;

/**
 * Clase basada en la clase OpenIdProviderService encontrada en
 * http://www.ibm.com/developerworks/java/library/j-openid2/index.html
 */
public class OpenIdProviderService {
	
	private static Logger logger =
        LoggerFactory.getLogger(OpenIdProviderService.class);
  
	private static ServerManager serverManager;
  
	/**
	 *configura y devuelve una referencia a la clase ServerManager de openid4java.
	 */
	public static ServerManager getServerManager() {
		logger.trace("geterverManager() BEGIN...");
		if (serverManager == null) {
			serverManager = new ServerManager();
			serverManager.setOPEndpointUrl(getOpEndpointUrl());
			serverManager.setPrivateAssociations(new InMemoryServerAssociationStore());
			serverManager.setSharedAssociations(new InMemoryServerAssociationStore());
		}
		logger.debug("Returning ServerManager =>" + serverManager.toString() + "<=");
		logger.trace("geterverManager() END...");
		return serverManager;
	}
  
	/**
	 * This is your OP endpoint.
	 */
	public static String getOpEndpointUrl() {
		String serverURL = (String) ConfigurationHolder.getFlatConfig().get("grails.serverURL");
		return serverURL;
	}

	/**
	 * processAssociationRequest() utiliza openid4java para asociar el proveedor a la petición de la Parte Confiante.
	 * @param response
	 * @param request
	 * @throws IOException
	 */
	public static void processAssociationRequest(ServletResponse response, ParameterList request) throws IOException {
		logger.trace("processAssociationRequest() BEGIN...");
		Message message = getServerManager().associationResponse(request);
		sendPlainTextResponse(response, message);
		logger.trace("processAssociationRequest() END...");
	}

	/**
	 * This is a helpful method to enable if you want to see what is being
	 * sent across. Disable this in production.
	 * 
	 * @param request
	 */
	@SuppressWarnings("unchecked")
	public static void logRequestParameters(ParameterList request) {
		logger.trace("logRequestParameters() BEGIN...");
		if (logger.isDebugEnabled()) {
			logger.debug("Dumping request parameters:");
			List<Parameter> paramList = request.getParameters();
			for (Parameter parameter : paramList) {
				logger.debug(parameter.getKey() + ":" + parameter.getValue());
			}
		}
		logger.trace("logRequestParameters() END...");
	}

  	/**
   	* creates the openid4java AuthResponse message that is sent to the RP following an authentication request. 
   	*/
  	public static Message createAuthResponse(ParameterList requestParameters, String userSelectedId, String userSelectedClaimedId,
  			boolean authenticatedAndApproved) {
  		logger.trace("createAuthResponse() BEGIN...");
  		logger.debug("Creating Auth Response. UserSelectedId =>" 
  				+ userSelectedId + "<= userSelectedClaimedId =>" 
  				+ userSelectedClaimedId + "<=");
  		Message authResponse = getServerManager().authResponse(
  				requestParameters, userSelectedId, userSelectedClaimedId, authenticatedAndApproved);
  		logger.trace("createAuthResponse() END...");
  		return authResponse;
  	}

  	private static void sendPlainTextResponse(ServletResponse response, Message message) throws IOException {
  		logger.trace("sendPlainTextResponse() BEGIN...");
  		response.setContentType("text/plain");
  		OutputStream os = response.getOutputStream();
  		os.write(message.keyValueFormEncoding().getBytes());
  		os.close();
  		logger.trace("sendPlainTextResponse() END...");
  	}

  	/**
  	 * This is where the bulk of the action happens. Once the handshaking is
  	 * done and the OP is ready to send the response back to the requester, this
  	 * method is used to pack it all up and get it ready to ship back.
  	 * 
  	 * See comments below for more details.
  	 * 
  	 * @param requestParameters
  	 * @param userSelectedId
  	 * @param userSelectedClaimedId
  	 * @param registrationModel
  	 * @return
  	 */
  	public static Message buildAuthResponse(ParameterList requestParameters, 
  			String userSelectedId, String userSelectedClaimedId, 
  			RegistrationModel registrationModel) {
  		logger.debug("buildAuthResponse() BEGIN...");
  		Message authResponse = OpenIdProviderService.createAuthResponse(
  				requestParameters,
  				userSelectedId,
  				userSelectedClaimedId,
  				true // authenticated... yep
        );
  		// Check for and process any extensions the RP has asked for
  		AuthRequest authRequest = null;
  		try {
  			authRequest = AuthRequest.createAuthRequest(requestParameters, getServerManager().getRealmVerifier());
  			// Process all of the extensions we care about...
  			// Simple Registration
  			if (authRequest.hasExtension(SRegMessage.OPENID_NS_SREG)) {
  				logger.debug("Processing Simple Registration Extension request...");
  				MessageExtension extensionRequestObject = authRequest.getExtension(SRegMessage.OPENID_NS_SREG);
  				if (extensionRequestObject instanceof SRegRequest) {
  					SRegRequest sRegRequest = (SRegRequest)extensionRequestObject;
  					// Send back everything we have (which you'll notice is not everything)
  					/// required or not. We're friendly...
  					Map<String, String> registrationData = new HashMap<String, String>();
  					registrationData.put("email", registrationModel.getEmailAddress());
  					registrationData.put("fullname", registrationModel.getFullName());
  					SRegResponse sRegResponse = SRegResponse.createSRegResponse(sRegRequest, registrationData);
  					authResponse.addExtension(sRegResponse);
  				} else {
  					logger.error("Cannot continue processing Simple Registration Extension. The object returned from the AuthRequest (of type " + extensionRequestObject.getClass().getName() + ") claims to be correct, but is not of type " + SRegRequest.class.getName() + " as expected.");
  				}
  			}
  			if (authRequest.hasExtension(AxMessage.OPENID_NS_AX)) {
  				logger.debug("Processing Attribute Exchange request...");
  				MessageExtension extensionRequestObject = authRequest.getExtension(AxMessage.OPENID_NS_AX);
  				FetchResponse fetchResponse = null;
  				Map<String, String> axData = new HashMap<String, String>();
  				if (extensionRequestObject instanceof FetchRequest) {
  					FetchRequest axRequest = (FetchRequest)extensionRequestObject;
  					ParameterList parameters = axRequest.getParameters();
  					fetchResponse = FetchResponse.createFetchResponse(axRequest, axData);
  					if (parameters.hasParameter("type.email")) {
  						axData.put("email", registrationModel.getEmailAddress());
  						fetchResponse.addAttribute("email", "http://schema.openid.net/contact/email", registrationModel.getEmailAddress()); 						
  					}
  					if (parameters.hasParameter("type.transaction")) {
  						axData.put("transaction", registrationModel.getTransaction());
  						fetchResponse.addAttribute("transaction", "http://www.gruposp2p.org/schema/transaction", registrationModel.getTransaction()); 						
  					}
  					if (parameters.hasParameter("type.nif")) {
  						axData.put("transaction", registrationModel.getTransaction());
  						fetchResponse.addAttribute("transaction", "http://www.gruposp2p.org/schema/transaction", registrationModel.getTransaction()); 						
  					}
  					if (parameters.hasParameter("type.username")) {
  						axData.put("transaction", registrationModel.getTransaction());
  						fetchResponse.addAttribute("transaction", "http://www.gruposp2p.org/schema/transaction", registrationModel.getTransaction()); 						
  					} 	
  					if (parameters.hasParameter("type.phone")) {
  						axData.put("transaction", registrationModel.getTransaction());
  						fetchResponse.addAttribute("transaction", "http://www.gruposp2p.org/schema/transaction", registrationModel.getTransaction()); 						
  					} 	
  					authResponse.addExtension(fetchResponse);
  			} else {
        		logger.error("Cannot continue processing Attribute Exchange (AX) request. The object returned from the AuthRequest (of type " + extensionRequestObject.getClass().getName() + ") claims to be correct, but is not of type " + AxMessage.class.getName() + " as expected.");
        	}
  		}
  			getServerManager().sign((AuthSuccess)authResponse);
      	// TODO: Add Custom extension where we send back Favorite Color
  		} catch (Exception e) {
    		logger.error("Error occurred creating AuthRequest object:", e);
    	}
    	logger.debug("buildAuthResponse() End...");
    	return authResponse;
  	}

	public static Message verifyCheckAuthentication (ParameterList request) {
		return serverManager.verify(request);
	}
  	
}